package lil.llahn.security.common.asymmetric;

import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * @ClassName: SecurityTools
 * @Description: TODO  非对称加密,加密密钥分为公钥和私钥。可以使用公钥加密私钥解密，也可以使用私钥加密公钥解密
 * @Date: 2019/11/8 15:37
 * @Auther: lil
 */
@Slf4j
public class SecurityTools {
    private final static String UTF8 = "UTF-8";

    private static String formatString(String source) {
        if (source == null) return null;
        return source.replaceAll("\\r", "").replaceAll("\\n", "");
    }

    /**
     * todo RSA公钥加密
     *
     * @param data      明文字符串
     * @param publicKey 公钥
     * @return
     * @throws Exception
     */
    public static String encryptByPublicKey(String data, String publicKey, Algo algo) {
        try {
            publicKey = formatString(publicKey);
            //base64编码的公钥
            byte[] decoded = Base64.getDecoder().decode(publicKey);
            RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(algo.name()).generatePublic(new X509EncodedKeySpec(decoded));
            //RSA加密
            Cipher cipher = Cipher.getInstance(algo.name());
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            String outStr = Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes(UTF8)));
            return outStr;
        } catch (Exception e) {
            log.error("Failed to encryptByPublicKey data.\r\ndata={}\r\npublicKey:{}\r\nalgo:{}", data, publicKey, algo, e);
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * todo RSA私钥解密
     *
     * @param data       加密字符串
     * @param privateKey 私钥
     * @return
     * @throws Exception
     */
    public static String decryptByPrivateKey(String data, String privateKey, Algo algo) {
        try {
            privateKey = formatString(privateKey);
            //64位解码加密后的字符串
            byte[] inputByte = Base64.getDecoder().decode(data.getBytes(UTF8));
            // base64编码的私钥
            byte[] decoded = Base64.getDecoder().decode(privateKey);
            RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(algo.name()).generatePrivate(new PKCS8EncodedKeySpec(decoded));
            // RSA解密
            Cipher cipher = Cipher.getInstance(algo.name());
            cipher.init(Cipher.DECRYPT_MODE, priKey);
            String outStr = new String(cipher.doFinal(inputByte));
            return outStr;
        } catch (Exception e) {
            log.error("Failed to decryptByPrivateKey data.\r\ndata={}\r\nprivateKey:{}\r\nalgo:{}", data, privateKey, algo, e);
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * todo 私钥加密
     *
     * @param data       明文数据
     * @param privateKey 私钥
     * @return
     */
    public static String encryptByPrivateKey(String data, String privateKey, Algo algo) {
        try {
            privateKey = formatString(privateKey);
            byte[] kb = Base64.getDecoder().decode(privateKey.getBytes(UTF8));
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(kb);
            KeyFactory keyFactory = KeyFactory.getInstance(algo.name());
            PrivateKey key = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] b = data.getBytes(UTF8);
            byte[] encrypt = cipher.doFinal(b);
            return Base64.getEncoder().encodeToString(encrypt);
        } catch (Exception e) {
            log.error("Failed to encryptByPublicKey data.\r\ndata={}\r\nprivateKey:{}\r\nalgo:{}", data, privateKey, algo, e);
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * todo 公钥解密
     *
     * @param data      加密数据
     * @param publicKey 公钥
     * @return
     */
    public static String decryptByPublicKey(String data, String publicKey, Algo algo) {
        try {
            publicKey = formatString(publicKey);
            byte[] kb = Base64.getDecoder().decode(publicKey.getBytes(UTF8));
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(kb);
            KeyFactory keyFactory = KeyFactory.getInstance(algo.name());
            PublicKey key = keyFactory.generatePublic(x509EncodedKeySpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] b = data.getBytes(UTF8);
            byte[] decrypt = cipher.doFinal(Base64.getDecoder().decode(b));
            return new String(decrypt, UTF8);
        } catch (Exception e) {
            log.error("Failed to decryptByPublicKey data.\r\ndata={}\r\npublicKey:{}\r\nalgo:{}", data, publicKey, algo, e);
            throw new RuntimeException(e.getMessage(), e);
        }
    }
}
